package main

import (
	"unsafe"
	"bytes"
	"encoding/binary"
	"math"
)

const (
	_LFDataBufferType_SmallIntBegin         int = 0
	_LFDataBufferType_SmallIntEnd               = 127
	_LFDataBufferType_BigIntBegin               = 128
	_LFDataBufferType_BigIntEnd                 = 135
	_LFDataBufferType_BigIntNegativeBegin       = 136
	_LFDataBufferType_BigIntNegativeEnd         = 143
	_LFDataBufferType_False                     = 144
	_LFDataBufferType_True                      = 145
	_LFDataBufferType_Float                     = 148
	_LFDataBufferType_Double                    = 152
	_LFDataBufferType_Null                      = 153
	_LFDataBufferType_StringBegin               = 160
	_LFDataBufferType_BigString                 = 172
	_LFDataBufferType_StringEnd                 = 175
	_LFDataBufferType_BinaryBegin               = 176
	_LFDataBufferType_BigBinary                 = 188
	_LFDataBufferType_BinaryEnd                 = 191
	_LFDataBufferType_ArrayBegin                = 192
	_LFDataBufferType_BigArray                  = 204
	_LFDataBufferType_ArrayEnd                  = 207
	_LFDataBufferType_TableBegin                = 208
	_LFDataBufferType_BigTable                  = 220
	_LFDataBufferType_TableEnd                  = 223
	_LFDataBufferType_IndexBegin                = 224
	_LFDataBufferType_BigIndex                  = 236
	_LFDataBufferType_IndexEnd                  = 239
	_LFDataBufferType_SmallIntNegativeBegin     = 240
)

func (p *LFData) getType(char int) LFDataType {
	if char >= _LFDataBufferType_SmallIntBegin && char <= _LFDataBufferType_SmallIntEnd {
		return LFDataType_u8
	}
	if char >= _LFDataBufferType_BigIntBegin && char <= _LFDataBufferType_BigIntEnd {
		return LFDataType_u64
	}
	if char >= _LFDataBufferType_BigIntNegativeBegin && char <= _LFDataBufferType_BigIntNegativeEnd {
		return LFDataType_s64
	}
	if char == _LFDataBufferType_False {
		return LFDataType_false
	}
	if char == _LFDataBufferType_True {
		return LFDataType_true
	}
	if char == _LFDataBufferType_Float {
		return LFDataType_f32
	}
	if char == _LFDataBufferType_Double {
		return LFDataType_f64
	}
	if char == _LFDataBufferType_Null {
		return LFDataType_null
	}
	if char >= _LFDataBufferType_StringBegin && char <= _LFDataBufferType_StringEnd {
		return LFDataType_string
	}
	if char >= _LFDataBufferType_BinaryBegin && char <= _LFDataBufferType_BinaryEnd {
		return LFDataType_binary
	}
	if char >= _LFDataBufferType_ArrayBegin && char <= _LFDataBufferType_ArrayEnd {
		return LFDataType_array
	}
	if char >= _LFDataBufferType_TableBegin && char <= _LFDataBufferType_TableEnd {
		return LFDataType_table
	}
	if char >= _LFDataBufferType_IndexBegin && char <= _LFDataBufferType_IndexEnd {
		return LFDataType_index
	}
	if char >= _LFDataBufferType_SmallIntNegativeBegin {
		return LFDataType_s8
	}
	return -1
}

func (p *LFData) getSize(buf []byte, char int, big int) (offset int, sz int) {
	size := uint64(char & 0x0f)

	bigsz := uint64(big & 0x0f)
	if size >= bigsz {
		var lf LFByteInt
		offset = lf.ReadFromBuf(buf, int(size-bigsz+1))
		if offset < 0 {
			panic(0)
		}
		size = lf.Value()
		if uint64(len(buf)-offset) < size {
			panic(0)
		}
	}
	sz = int(size)
	return offset, sz
}

func (p *LFData) ReadFromBuf(buf []byte) int {
	p.Clear()
	char := int((uint8)(buf[0]))
	idx := 1

	//fmt.Println("b", b)

	ty := p.getType(char)
	switch ty {
	case LFDataType_null:
		//do nothing

	case LFDataType_true:
		p.fastSetBool(true)

	case LFDataType_false:
		p.fastSetBool(false)

	case LFDataType_s8:
		p.fastSetInt8(int8(char-256))

	case LFDataType_s64:
		sz := char - _LFDataBufferType_BigIntNegativeBegin + 1
		var lf_int LFByteInt
		sz = lf_int.ReadFromBuf(buf[idx:], sz)
		ti := ^lf_int.Value()
		p.fastSetInt64(*(*int64)(unsafe.Pointer(&ti)));
		idx += sz

	case LFDataType_u8:
		p.fastSetUint8(uint8(char))

	case LFDataType_u64:
		sz := char - _LFDataBufferType_BigIntBegin + 1
		var lf_int LFByteInt
		sz = lf_int.ReadFromBuf(buf[idx:], sz)
		p.fastSetUint64(lf_int.Value());
		idx += sz

	case LFDataType_f32:
		bits := binary.LittleEndian.Uint32(buf[idx:])
		p.fastSetFloat32(math.Float32frombits(bits))
		idx += 4

	case LFDataType_f64:
		bits := binary.LittleEndian.Uint64(buf[idx:])
		p.fastSetFloat64(math.Float64frombits(bits))
		idx += 8

	case LFDataType_string:
		offset, sz := p.getSize(buf[idx:], char, _LFDataBufferType_BigString)
		idx += offset
		p.fastSetString(string(buf[idx : idx+sz]))
		idx += sz

	case LFDataType_binary:
		offset, sz := p.getSize(buf[idx:], char, _LFDataBufferType_BigBinary)
		idx += offset
		p.fastSetBinary(buf[idx : idx+sz])
		idx += sz

	case LFDataType_array:
		offset, sz := p.getSize(buf[idx:], char, _LFDataBufferType_BigArray)
		idx += offset

		p.MakeArray()
		for i := 0; i < sz; i++ {
			var data LFData
			size := data.ReadFromBuf(buf[idx:])
			idx += size
			p.fastSetToArray(&data)
		}

	case LFDataType_table:
		offset, sz := p.getSize(buf[idx:], char, _LFDataBufferType_BigTable)
		idx += offset

		p.MakeTable()
		for i := 0; i < sz; i++ {
			var key LFData
			size := key.ReadFromBuf(buf[idx:])
			idx += size
			if !key.IsString() {
				return -1
			}

			var data LFData
			size = data.ReadFromBuf(buf[idx:])
			if size < 0 {
				return -1
			}

			idx += size
			p.fastSetToTable(key.JustString(), &data)
		}

	case LFDataType_index:
		offset, sz := p.getSize(buf[idx:], char, _LFDataBufferType_BigIndex)
		idx += offset

		p.MakeIndex()
		for i := 0; i < sz; i++ {
			var index LFData
			size := index.ReadFromBuf(buf[idx:])
			if size < 0 || !index.IsInteger() {
				return -1
			}
			idx += size
			
			var data LFData
			size = data.ReadFromBuf(buf[idx:])
			if size < 0 {
				return -1
			}

			idx += size
			p.fastSetToIndex(index.AsInt64(), &data)
		}
	default:
		return -1
	}

	return idx
}

func (p* LFData) WriteToBuf() []byte {
	var buffer bytes.Buffer
	if !p.WriteToBuffer(&buffer) {
		return nil
	}
	return buffer.Bytes()
}

func (p *LFData) WriteToBuffer(buf *bytes.Buffer) bool {
	switch p.dtype {
	case LFDataType_null:
		binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_Null))
	case LFDataType_false:
		binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_False))
	case LFDataType_true:
		binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_True))
	case LFDataType_u8:
		fallthrough
	case LFDataType_u16:
		fallthrough
	case LFDataType_u32:
		fallthrough
	case LFDataType_u64:
		p.wirteUint(buf, p.val.(uint64))
	case LFDataType_s8:
		fallthrough
	case LFDataType_s16:
		fallthrough
	case LFDataType_s32:
		fallthrough
	case LFDataType_s64:
		p.wirteInt(buf, p.val.(int64))
	case LFDataType_f32:
		binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_Float))
		binary.Write(buf, binary.LittleEndian, (float32)(p.val.(float64)))
	case LFDataType_f64:
		binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_Double))
		binary.Write(buf, binary.LittleEndian, p.val.(float64))
	case LFDataType_string:
		v := p.val.(string)
		sz := len(v)
		if sz < int(_LFDataBufferType_BigString&0x0f) {
			binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_StringBegin)+uint8(sz))
		} else if sz > u32_max {
			return false
		} else {
			var lf_int LFByteInt
			lf_int.Set(uint64(sz))
			int_buf := lf_int.WriteToBuf()
			binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_BigString+len(int_buf)-1))
			binary.Write(buf, binary.LittleEndian, int_buf)
		}
		binary.Write(buf, binary.LittleEndian, []byte(v))
	case LFDataType_binary:
		v := p.val.([]byte)
		sz := len(v)
		if sz < int(_LFDataBufferType_BigBinary&0x0f) {
			binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_BinaryBegin)+uint8(sz))
		} else if sz > u32_max {
			return false
		} else {
			var lf_int LFByteInt
			lf_int.Set(uint64(sz))
			int_buf := lf_int.WriteToBuf()
			binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_BigBinary+len(int_buf)-1))
			binary.Write(buf, binary.LittleEndian, int_buf)
		}
		binary.Write(buf, binary.LittleEndian, v)
	case LFDataType_array:
		v := p.val.(_ArrayST)
		sz := len(v)
		if sz < _LFDataBufferType_BigTable&0x0f {
			binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_ArrayBegin+sz))
		} else if sz > u32_max {
			return false
		} else {
			var lf_int LFByteInt
			lf_int.Set(uint64(sz))
			int_buf := lf_int.WriteToBuf()
			binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_BigArray+len(int_buf)-1))
			binary.Write(buf, binary.LittleEndian, int_buf)
		}
		for i := 0; i < sz; i++ {
			(v)[i].WriteToBuffer(buf)
		}
	case LFDataType_table:
		v := p.val.(*_TableST)
		sz := len(*v)
		if sz < _LFDataBufferType_BigTable&0x0f {
			binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_TableBegin+sz))
		} else if sz > u32_max {
			return false
		} else {
			var lf_int LFByteInt
			lf_int.Set(uint64(sz))
			int_buf := lf_int.WriteToBuf()
			binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_BigTable+len(int_buf)-1))
			binary.Write(buf, binary.LittleEndian, int_buf)
		}
		for k, v := range *v {
			var vk LFData
			vk.Set(k)
			vk.WriteToBuffer(buf)
			v.WriteToBuffer(buf)
		}
	case LFDataType_index:
		v := p.val.(*_IndexST)
		sz := len(*v)
		if sz < _LFDataBufferType_BigIndex&0x0f {
			binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_IndexBegin+sz))
		} else if sz > u32_max {
			return false
		} else {
			var lf_int LFByteInt
			lf_int.Set(uint64(sz))
			int_buf := lf_int.WriteToBuf()
			binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_BigIndex+len(int_buf)-1))
			binary.Write(buf, binary.LittleEndian, int_buf)
		}
		for k, v := range *v {
			var vk LFData
			vk.Set(k)
			vk.WriteToBuffer(buf)
			v.WriteToBuffer(buf)
		}
	}
	return true
}

func (p *LFData) wirteUint(buf *bytes.Buffer, value uint64) bool {
	if value <= uint64(_LFDataBufferType_SmallIntEnd) {
		binary.Write(buf, binary.LittleEndian, uint8(value))
		return true
	}

	var lf_int LFByteInt
	lf_int.Set(value)
	int_buf := lf_int.WriteToBuf()
	binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_BigIntBegin+len(int_buf)-1))
	binary.Write(buf, binary.LittleEndian, int_buf)
	return true
}

func (p *LFData) wirteInt(buf *bytes.Buffer, value int64) bool {
	if value < 0 {
		if value >= int64(_LFDataBufferType_SmallIntNegativeBegin)-256 {
			binary.Write(buf, binary.LittleEndian, int32(value))
		} else {
			var lf_int LFByteInt
			uv := uint64(value)
			lf_int.Set(^uv)
			int_buf := lf_int.WriteToBuf()
			binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_BigIntNegativeBegin+len(int_buf)-1))
			binary.Write(buf, binary.LittleEndian, int_buf)
		}
	} else {
		uv := uint64(value)
		if uv <= uint64(_LFDataBufferType_SmallIntBegin) {
			binary.Write(buf, binary.LittleEndian, uint8(uv))
		} else {
			var lf_int LFByteInt
			lf_int.Set(uint64(value))
			int_buf := lf_int.WriteToBuf()
			binary.Write(buf, binary.LittleEndian, uint8(_LFDataBufferType_BigIntBegin+len(int_buf)-1))
			binary.Write(buf, binary.LittleEndian, int_buf)
		}
	}
	return true
}
